#include <stdlib.h>
#include "graph.h" //per INFINITE_WEIGHT
#include <limits.h>
#include "remove.h"
#include "math.h"
#include "operations.h"
#define DEBUG 0
#define NOT_SET -1
#define CANDIDATE_FAIL -10000 //bello lontano dai delta +/-
#define CLEAN_3TIMES_EDGES

// --- SOLVE  --------------------------------------------------------------------------------------------------------------------

int solveRemoveNeighbourhood(Pgraph pg, PvehicleState pvs, PGMatrix pgm, int k, PtabooList ptl, TabooExpire now,
								pLOSConfiguration pLOSconfig, bool notWorseningMoves){

	/** Cache del migliore REMOVE che si è trovato finora. best.bestJk è univoco per lo stato di scelta non compiuta. */
	PbestAddRemoveChoice pbest;
	pbest = malloc(sizeof(BestAddRemoveChoice));
	/** Miglior punteggio ottenibile. Se negativo, indica mosse peggiorative (TODO test la verità di questa frase) */
	pbest->bestScore = NOT_SET;
	/** archi 'perno' su cui esplorare triangolazioni. e_{il}, e_{jl} \in P_k, in documentazione */
	PedgeID ppid_array[pvs->pathSize];
	PedgeID pid_il, pid_jl, pid_ij, temp;
	int times_il, times_jl; // quante volte gli archi il e jl sono percorsi nel vehiclePath. valore cache, perchè si potrebbero fare modifiche future

	int shift = 0; // quanti archi saltare durante il ciclo interno alla la doppia percorrenza del Path.
	int i,j; //appoggio per ciclare fino a shift
	Weight tempScore; // appoggio per aggiornamento candidato migliore
	int initialJk = pvs->jk; //appoggio per stato di uscita

	resetMemory(pvs->pvp, DIRECTION_NEXT);
	for(i=0; i<pvs->pathSize; i++) {
		ppid_array[i] = getNearEdgeInVehiclePath(pvs->pvp, DIRECTION_NEXT)->id;
		/* TODO blocco da testare. A ogni remove controlla quegli archi percorsi un numero di volte multiplo di tre, e li rimuove due volte
		 * dal percorso perchè inutili "accumulamenti" di mosse precedenti. Pat ci ha ragionato e crede che questa operazione non sconnetta
		 * in alcun modo nè occasione il grafo! Si noti che la tenure di rimozione è nulla perchè la mossa sia "trasparente" agli occhi
		 *  di tutto il resto */
		#ifdef CLEAN_3TIMES_EDGES
			temp = ppid_array[i];
			if (!getEdgeWalkedTimesInVehiclePath(temp, pvs->pvp)%3) {
				removeEdgeOp(temp, getEdgeGraph(pg, temp), pvs, pgm, k, ptl, now, 0);
				removeEdgeOp(temp, getEdgeGraph(pg, temp), pvs, pgm, k, ptl, now, 0);
			}
		#endif
	}

	for(i=0; i<(pvs->pathSize-1); i++) {

		pid_il = ppid_array[i];
		for(j=(i+1); j<pvs->pathSize; j++) {

			pid_jl = ppid_array[j];
			if(areConsecutive(pid_il, pid_jl)) {

				times_il = getEdgeWalkedTimesInVehiclePath(pid_il, pvs->pvp);
				times_jl = getEdgeWalkedTimesInVehiclePath(pid_jl, pvs->pvp);

				pid_ij = getEdgeTriangulating(pid_il, pid_jl);
				#if DEBUG > 2
					//mostro i candidati di questa iterazione
					printf("candidati: toremove1 ");
					printEdgeID(stdout, pid_il); printf(" toremove2 ");printEdgeID(stdout, pid_jl);
					printf(" toadd ");printEdgeID(stdout, pid_ij); printf("\n");
					printf("PRIMA DELLE AGGIUNTE\n");
					printVehiclePath(stdout,pvs->pvp);
				#endif
				removeEdgeFromVehicleStateForConnectionTest(pid_il, pvs);
				removeEdgeFromVehicleStateForConnectionTest(pid_jl, pvs);
				addEdgeToVehicleStateForConnectionTest(pid_ij, pvs);
				#if DEBUG > 2
					printf("DOPO LE AGGIUNTE\n");
					printVehiclePath(stdout,pvs->pvp);
				#endif
				bool isPathConn = isConnected(pvs->pvp, pvs->lEdges);
				//resetto la mossa a prescindere
				addEdgeToVehicleStateForConnectionTest(pid_il, pvs);
				addEdgeToVehicleStateForConnectionTest(pid_jl, pvs);
				removeEdgeFromVehicleStateForConnectionTest(pid_ij, pvs);

				if (!isPathConn)
					continue;

				#if DEBUG > 1
					printf("test di connessione passato!\n");
				#endif
				tempScore = analyzeTriangleEdgeRemoveCandidate(pg, pid_ij, pid_il, pid_jl, pvs, pgm, k, ptl, now,
																times_il, times_jl, pLOSconfig->evaluate);

				if(tempScore!=CANDIDATE_FAIL)
					if(pbest->bestScore==NOT_SET || tempScore >= pbest->bestScore) {
						updateBestRemoveChoice(pbest, *pid_il, *pid_jl, *pid_ij, tempScore);
						#if DEBUG > 1
							printf("updated!\n");
						#endif
					}
				freeEdgeID(pid_ij);
			}
		}
	}
	if(pbest->bestScore!=NOT_SET) { //TODO copiare in ADD e SWAP
		if(!notWorseningMoves)
			makeRemoveMove(pg, pvs, pgm, k, ptl, now, pbest, pLOSconfig->addEdgeTenure, pLOSconfig->removeEdgeTenure);
		if(pvs->jk >= initialJk) {
			if(notWorseningMoves)
				makeRemoveMove(pg, pvs, pgm, k, ptl, now, pbest, pLOSconfig->addEdgeTenure, pLOSconfig->removeEdgeTenure);
			if(pvs->jk > initialJk)
				return MOVE_STATUS_IMPROVE;
			else
				return MOVE_STATUS_UNCHANGED;
		}
		else
			return MOVE_STATUS_WORSEN;
	}
	return MOVE_STATUS_FAIL;
}

// --- AGGIORNAMENTI ------------------------------------------------------------------------------------------------------------------

/** Snellimento del codice. aggiorna una scelta migliore, \ref{BestChoice} */
void updateBestRemoveChoice(PbestAddRemoveChoice pbc, EdgeID e_il, EdgeID e_jl, EdgeID e_ij, Weight score) {

	pbc->e_il.i = e_il.i; pbc->e_il.j = e_il.j;
	pbc->e_jl.i = e_jl.i; pbc->e_jl.j = e_jl.j;
	pbc->e_ij.i = e_ij.i; pbc->e_ij.j = e_ij.j;
	pbc->bestScore = score;
}

void makeRemoveMove(Pgraph pg, PvehicleState pvs, PGMatrix pgm, int k, PtabooList ptl, TabooExpire now, PbestAddRemoveChoice pbest, int addTenure, int removeTenure) {

	#if DEBUG > 0
		printf("-e_il: "); printEdgeID(stdout, &pbest->e_il); printEdgeGraph(stdout, getEdgeGraph(pg, &pbest->e_il));
		printf("-e_jl: "); printEdgeID(stdout, &pbest->e_jl); printEdgeGraph(stdout, getEdgeGraph(pg, &pbest->e_jl));
		printf("+e_ij: "); printEdgeID(stdout, &pbest->e_ij); printEdgeGraph(stdout,  getEdgeGraph(pg, &pbest->e_ij));
	#endif

	removeEdgeOp(&pbest->e_il, getEdgeGraph(pg, &pbest->e_il), pvs, pgm, k, ptl, now, removeTenure);
	removeEdgeOp(&pbest->e_jl, getEdgeGraph(pg, &pbest->e_jl), pvs, pgm, k, ptl, now, removeTenure);
	addEdgeOp(&pbest->e_ij, getEdgeGraph(pg, &pbest->e_ij), pvs, pgm, k, ptl, now, addTenure, true);
}

// --- TRIANGOLAZIONI --------------------------------------------------------------------------------------------------------------------
// ritorna CANDIDATE_FAIL ( = -INFINITE_WEIGHT ) in caso di fallimento
Weight analyzeTriangleEdgeRemoveCandidate(Pgraph pg, PedgeID pid_ij, PedgeID pid_il, PedgeID pid_jl,
									 PvehicleState pvs, PGMatrix pgm, int k, PtabooList ptl, TabooExpire now,
									 int times_il, int times_jl, int evaluateMode) {
	Weight newScore = CANDIDATE_FAIL;
	int oldJk = pvs->jk;
	int newJk = oldJk;

	/** Valutazione per gli archi che non profittano o per loro caratteristica o perchè comunque non possono essere raccolti. */
	int evaluateNonProfitablesMode;
	if(evaluateMode==EVAL_PURE)
		evaluateNonProfitablesMode = EVAL_PURE;
	else
		evaluateNonProfitablesMode = EVAL_SMART;

	if (isTQOKTriangleRemove(pg, pvs, pgm, pid_ij, pid_il, pid_jl, k)){
		if(!isEdgeTaboo(ptl, pid_il, now) && !isEdgeTaboo(ptl, pid_jl, now) && !isEdgeTaboo(ptl, pid_ij, now) ) {

			newScore = 0;
			#if DEBUG > 2
				printf("\nIL: "); printEdgeGraph(stdout, getEdgeGraph(pg, pid_il)); // ~~~ arco sinistro
			#endif
			if( amIGatheringEdge(pgm, pid_il, k) && times_il==1 ) { //OCIO aggiunta isWalkedOnce in data 20 giugno
				//printf("\tevaluating IL(-G): %f\n", -evaluateEdge(pg, pid_il, pvs->tau, pvs->theta, EVAL_REMOVE_PROFITABLES));
				newScore -= evaluateEdge(pg, pid_il, pvs->tau, pvs->theta, evaluateMode);
				newJk -= evaluateEdge(pg, pid_il, pvs->tau, pvs->theta, EVAL_PURE); // per forza puro!
			}
			else { /* la raccolta e il servizio non son più interessanti;
						o perchè già non son raccolti
						o perchè l'arco non è profittabile (implica la prima)
						o perchè anche dopo il remove raccolgo lo stesso!!
					Pertanto il punteggio si alza più l'arco rimosso pesava come tempo. */
				//printf("\tevaluating IL(G~): %f\n", evaluateNonProfitableEdge(pg, pid_il, pvs->tau, EVAL_REMOVE_NON_PROFITABLES));
				newScore += evaluateNonProfitableEdge(pg, pid_il, pvs->tau, evaluateNonProfitablesMode); //aggiunge il tempo risparmiato
			}
			#if DEBUG > 2
				printf("IL::newScore = %f | newJk = %d\n", newScore, oldJk, newJk);
				// ~~~ arco destro
				printf("\nJL: "); printEdgeGraph(stdout, getEdgeGraph(pg, pid_jl));
			#endif
			if( amIGatheringEdge(pgm, pid_jl, k) && times_jl==1) { //OCIO aggiunta isWalkedOnce in data 20 giugno
				//printf("\tevaluating JL(-G): %f\n", -evaluateEdge(pg, pid_jl, pvs->tau, pvs->theta, EVAL_REMOVE_PROFITABLES));
				newScore -= evaluateEdge(pg, pid_jl, pvs->tau, pvs->theta, evaluateMode);
				newJk -= evaluateEdge(pg, pid_jl, pvs->tau, pvs->theta, EVAL_PURE); // per forza puro!
			}
			else { /* la raccolta e il servizio non son più interessanti;
						o perchè già non son raccolti
						o perchè l'arco non è profittabile
						o perchè anche dopo il remove raccolgo lo stesso!!
					Pertanto il punteggio si alza più l'arco rimosso pesava come tempo. */
				//printf("\tevaluating JL(G~): %f\n", evaluateNonProfitableEdge(pg, pid_jl, pvs->tau, EVAL_REMOVE_NON_PROFITABLES));
				newScore += evaluateNonProfitableEdge(pg, pid_jl, pvs->tau, evaluateNonProfitablesMode);
			}
			#if DEBUG > 2
				printf("JL::newScore = %f | newJk = %d\n", newScore, oldJk, newJk);

				printf("\nIJ: "); printEdgeGraph(stdout, getEdgeGraph(pg, pid_ij));
			#endif
			if( !isEdgeGathered(pgm, pid_ij) && isEdgeProfitable(pg, pid_ij) ) {
				//printf("\tevaluating IJ(+G): %f\n", evaluateEdge(pg, pid_ij, pvs->tau, pvs->theta, EVAL_REMOVE_PROFITABLES));
				newScore += evaluateEdge(pg, pid_ij, pvs->tau, pvs->theta, evaluateMode);
				newJk += evaluateEdge(pg, pid_ij, pvs->tau, pvs->theta, EVAL_PURE); // per forza puro!
			}
			else {//la raccolta e il servizio non son più interessanti; o perchè non son raccolti o perchè l'arco non è profittabile
				//printf("\tevaluating IJ(G~): %f\n", -evaluateNonProfitableEdge(pg, pid_ij, pvs->tau, EVAL_REMOVE_NON_PROFITABLES));
				//negativo, poichè l'inclusione in questo caso è sicuramente peggiorativa!
				newScore -= evaluateNonProfitableEdge(pg, pid_ij, pvs->tau, evaluateNonProfitablesMode);
			}
			#if DEBUG > 2
				printf("IJ::newScore = %f | newJk = %d\n", newScore, oldJk, newJk);
			#endif
		}
		#if DEBUG > 1
		else {
			printf("\nvoglio rimuovere (ma taboo): %d, %d, %d ",
					isEdgeTaboo(ptl, pid_il, now), isEdgeTaboo(ptl, pid_jl, now), isEdgeTaboo(ptl, pid_ij, now));
			printEdgeID(stdout, pid_il);  printEdgeID(stdout, pid_jl); printEdgeID(stdout, pid_ij); printf("\n");
		}
		#endif
	}
	return newScore;
}

//-------- TQ OK CHECKS ------------------------------------------------------------------------------------------------------------
bool isTQOKTriangleRemove(Pgraph pg, PvehicleState pvs, PGMatrix pgm, PedgeID pe_ij, PedgeID pe_il, PedgeID pe_jl, int k){

	return	(pvs->theta+
				getDeltaLoadRemovingEdgeID(pg,pe_il,k,pgm,pvs)+
				getDeltaLoadRemovingEdgeID(pg,pe_jl,k,pgm,pvs)+
				getDeltaLoadAddingEdgeID(pg,pe_ij,pgm)
			<= pg->Q);
}
